﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Graphix.Charts.Base
{
    using System.Drawing;
    using System.Drawing.Drawing2D;
    using System.Drawing.Imaging;
    using System.Globalization;
    using System.IO;
    using System.Reflection;

    using Graphix.Elements;
    using Graphix.Helpers;
    using CommonHelpers;
    using SvgNet.SvgGdi;

    public abstract class Core
    {
        public bool AddJavascript { get; set; }

        protected ChartBorder oldBorder;

        #region Excluded colors

        /// <summary>
        /// The exluded colors.
        /// </summary>
        protected readonly Color[] exludedColors
            = new[]
                {
                    Color.AliceBlue,
                    Color.Aquamarine,
                    Color.Azure,
                    Color.Beige,
                    Color.AntiqueWhite,
                    Color.Black,
                    Color.Bisque,
                    Color.BlanchedAlmond,
                    Color.Cornsilk,
                    Color.Empty,
                    Color.FloralWhite,
                    Color.GhostWhite,
                    Color.Ivory,
                    Color.LavenderBlush,
                    Color.LemonChiffon,
                    Color.LightGoldenrodYellow,
                    Color.LightYellow,
                    Color.Linen,
                    Color.MintCream,
                    Color.MistyRose,
                    Color.Moccasin,
                    Color.NavajoWhite,
                    Color.OldLace,
                    Color.PaleGoldenrod,
                    Color.PapayaWhip,
                    Color.PeachPuff,
                    Color.SeaShell,
                    Color.Snow,
                    Color.White,
                    Color.WhiteSmoke,
                    Color.Transparent
                };
        #endregion

        private List<IElement> elementsToDraw;

        protected IGraphics Graphics { get; set; }

        public int Height { get; set; }

        public int Width { get; set; }

        public TextStyle TextStyle { get; set; }

        public ValuedTextStyle Title { get; set; }

        public Background Background { get; set; }

        protected void AddElement(IElement element)
        {
            this.elementsToDraw.Add(element);
        }

        public string Save()
        {
            this.DrawCoreChart();
            var svgGraphics = this.Graphics as SvgGraphics;
            if (svgGraphics != null)
            {
                if (this.AddJavascript)
                {
                    return this.SaveWithJavascript(svgGraphics.WriteSVGString());
                }
            }

            return string.Empty;
        }

        protected Bitmap _bmp;
        public void SaveToFile(string filename, ImageFormat format)
        {
            _bmp = new Bitmap(new Bitmap(this.Width, this.Height));
            this.Graphics = new GdiGraphics(System.Drawing.Graphics.FromImage(_bmp));
            this.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
            this.Save();

            _bmp.Save(filename, format);
        }

        public void SaveToStream(Stream stream, ImageFormat format)
        {
            this.DrawCoreChart();
            throw new NotImplementedException();
        }

        protected virtual void CheckCoreFonts()
        {
            // Default
            this.TextStyle = this.SetupFont(this.TextStyle, TextStyle.Default);
            this.Title = this.SetupFont(this.Title, TextStyle.Default);

            this.CheckFonts();
        }

        protected abstract void CheckFonts();

        public ChartBorder Border { get; set; }

        private bool drawn;

        private void DrawCoreChart()
        {
            this.PreDrawCore();

            if (!this.drawn)
            {
                this.DrawChart();
                this.drawn = true;
            }

            var temp = this.Graphics as SvgGraphics;
            if (temp != null)
            {
                temp.Height = this.Height;
                temp.Width = this.Width;
            }

            foreach (IElement element in this.elementsToDraw)
            {
                element.Draw(this.Graphics);
            }
        }

        protected abstract void DrawChart();

        /// <summary>
        /// Adds elements of title
        /// </summary>
        protected void DrawTitle()
        {
            if (this.Title != null && !string.IsNullOrEmpty(this.Title.Text))
            {
                float position;
                StringFormat format;
                PerformMove move = PerformMove.Right;

                if (this.Title.Alignment == Horizontal.Center)
                {
                    position = this.Border.Left + this.Border.Width / 2f;
                    format = new StringFormat { Alignment = StringAlignment.Center };
                    move |= PerformMove.Left | PerformMove.HalfX;
                }
                else if (this.Title.Alignment == Horizontal.Left)
                {
                    position = this.Border.Left;
                    format = new StringFormat { Alignment = StringAlignment.Near };
                    move |= PerformMove.Left;
                }
                else
                {
                    position = this.Border.Right;
                    format = new StringFormat { Alignment = StringAlignment.Far };
                }

                RectangleF measured = DefaultGraphics.Instance.MeasureDisplayString(this.Title.Text, this.Title.Font);
                ChartString cs = new ChartString(position,
                    this.Border.Top,
                    measured.Width, measured.Height, this.Title.Text,
                    this.Title.Font, this.Title.ColorBrush);
                cs.Format = format;
                cs.MoveWhen = move;

                this.Border.Top += measured.Height;

                this.AddElement(cs);
            }
        }

        private void PreDrawCore()
        {
            this.CheckCoreFonts();

            this.Border = new ChartBorder(0, this.Height,
                0, this.Width);
            this.Border.Changed += this.BorderChanged;
            this.oldBorder = new ChartBorder(this.Border.Top, this.Border.Bottom, this.Border.Left, this.Border.Right);

            this.Graphics.Clear(this.Background.Color);
            if (this.Background.Stroke != Color.Transparent && this.Background.StrokeWidth > 0)
            {
                float width = this.Background.StrokeWidth;
                this.Graphics.DrawRectangle(
                    new Pen(this.Background.Stroke, width), 0, 0,
                    this.Border.Width - width, this.Border.Height - width);

                this.Border.Left += width;
                this.Border.Right -= width;
                this.Border.Top += width;
                this.Border.Bottom -= width;
            }

            this.PreDraw();
        }

        protected virtual void PreDraw()
        {
        }

        public Core()
        {
            this.Title = new ValuedTextStyle();
            this.TextStyle = TextStyle.Default;

            this.Graphics = new SvgGraphics();
            this.elementsToDraw = new List<IElement>();
            this.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
            this.Background = new Background();
        }

        protected TextStyle SetupFont(TextStyle changed, TextStyle candidate = null)
        {
            if (candidate == null)
            {
                candidate = this.TextStyle;
            }
            if (candidate.Font == null)
            {
                candidate.Font = this.TextStyle.Font;
            }
            if (candidate.Color == default(Color))
            {
                candidate.Color = this.TextStyle.Color;
            }

            if (changed == null)
            {
                changed = candidate;
            }
            if (changed.Font == null)
            {
                changed.Font = candidate.Font;
            }
            if (changed.Color == default(Color))
            {
                changed.Color = candidate.Color;
            }

            return changed;
        }

        protected ValuedTextStyle SetupFont(ValuedTextStyle changed, TextStyle candidate = null)
        {
            if (candidate == null)
            {
                candidate = this.TextStyle;
            }
            if (candidate.Font == null)
            {
                candidate.Font = this.TextStyle.Font;
            }
            if (candidate.Color == default(Color))
            {
                candidate.Color = this.TextStyle.Color;
            }

            if (changed == null)
            {
                changed = new ValuedTextStyle(candidate.Font.Name,
                    candidate.Font.Size,
                    candidate.Color);
            }
            if (changed.Font == null)
            {
                changed.Font = candidate.Font;
            }
            if (changed.Color == default(Color))
            {
                changed.Color = candidate.Color;
            }

            return changed;
        }

        protected string SaveWithJavascript(string svgCode, bool minified = true)
        {
#if DEBUG
            minified = false;
#endif
            const string OutsideBegin = "<!DOCTYPE html><html><head><script>";
            string OutsideMiddle = "</script></head><body><div>";
            const string OutsideEnd = "</div></body></html>";

            Assembly assem = Assembly.GetAssembly(typeof(Scripts.Mockup));
            string outputCode = string.Empty;

            string min = string.Empty;
            if (minified)
            {
                min = ".min";
            }

            using (Stream stream = assem.GetManifestResourceStream(string.Format("Graphix.Scripts.IncludeJQuery{0}.js", min)))
            {
                if (stream != null)
                {
                    using (var reader = new StreamReader(stream))
                    {
                        outputCode = reader.ReadToEnd();
                    }
                }
            }
            //// var labels = [/*labels*/];
            string labels = this.JavascriptLabelsValues();
            
            string values = string.Empty;
            if (this.AddJavascript)
            {
                labels = string.Format("var labels = [{0}];", labels);
                OutsideMiddle = OutsideMiddle.Insert(
                        OutsideMiddle.IndexOf("</head>", StringComparison.Ordinal),
                        "<link rel=\"stylesheet\" type=\"text/css\" href=\"./opentip.css\"/>");
                using (Stream stream = assem.GetManifestResourceStream(string.Format("Graphix.Scripts.HandlingValues{0}.js", min)))
                {
                    if (stream != null)
                    {
                        using (var reader = new StreamReader(stream))
                        {
                            values = reader.ReadToEnd();
                        }
                    }
                }
            }
            else
            {
                labels = "var labels = [];";
            }

            
            string chartJavascript = this.ChartJavascript(assem, minified);
            string replaced = @"$(function(){})" + (minified ? string.Empty : ";");
            outputCode = string.Concat(OutsideBegin, labels, outputCode.Replace(replaced,
                string.Concat("$(function(){", values, chartJavascript, "});")), OutsideMiddle, svgCode, OutsideEnd);
            return outputCode;
        }

        protected virtual string ChartJavascript(Assembly assem, bool minified = true)
        {
            string min = string.Empty;
            if (minified)
            {
                min = ".min";
            }

            string inside = string.Empty;
            var resource = string.Format("Graphix.Scripts.{0}{1}.js", this.GetType().Name, min);
            using (Stream stream = assem.GetManifestResourceStream(resource))
            {
                if (stream != null)
                {
                    using (var reader = new StreamReader(stream))
                    {
                        inside = reader.ReadToEnd();
                    }
                }
            }

            return inside;
        }

        protected virtual string JavascriptLabelsValues()
        {
            return string.Empty;
        }

        /// <summary>
        /// Moves elements when border get resized
        /// </summary>
        /// <param name="sender">
        /// Sender of this change
        /// </param>
        /// <param name="e">
        /// Handled event args of type <see cref="ChangeEventArgs"/>
        /// </param>
        protected void BorderChanged(object sender, ChangeEventArgs e)
        {
            if (this.drawn)
            {
                return;
            }

            float topDifference = e.Top - this.oldBorder.Top;
            float bottomDifference = e.Bottom - this.oldBorder.Bottom;
            float leftDifference = e.Left - this.oldBorder.Left;
            float rightDifference = e.Right - this.oldBorder.Right;

            foreach (IElement element in this.elementsToDraw)
            {
                float xMove = 0;
                float yMove = 0;

                if (element.MoveWhen.HasFlag(PerformMove.Top)
                    && !topDifference.Equals(0f))
                {
                    yMove += topDifference;
                }

                if (element.MoveWhen.HasFlag(PerformMove.Bottom)
                    && !bottomDifference.Equals(0f))
                {
                    yMove += bottomDifference;
                }

                if (element.MoveWhen.HasFlag(PerformMove.Left)
                    && !leftDifference.Equals(0f))
                {

                    xMove += leftDifference;
                }

                if (element.MoveWhen.HasFlag(PerformMove.Right)
                    && !rightDifference.Equals(0f))
                {

                    xMove += rightDifference;
                }

                if (element.MoveWhen.HasFlag(PerformMove.HalfX))
                {
                    xMove /= 2f;
                }

                if (element.MoveWhen.HasFlag(PerformMove.HalfY))
                {
                    yMove /= 2f;
                }

                element.Move(xMove, yMove);
            }

            this.oldBorder = new ChartBorder(e.Top, e.Bottom, e.Left, e.Right);
        }
    }
}
